package org.helgoboss.bundle_project_facade

import org.helgoboss.commandlet._
import java.io.File
import org.helgoboss.project_data.ProjectData
import org.helgoboss.scala_additions.{FileTree, RichFile}
import org.helgoboss.scala_additions.Implicits._
import org.helgoboss.maven.Maven
import org.helgoboss.obr_facade._
import org.helgoboss.commandlet_manager.CommandletManager
import org.helgoboss.bundle_project_generator.BundleProjectGenerator
import java.io.OutputStream
import org.osgi.service.obr.RepositoryAdmin
import org.helgoboss.scala_slf4j.Slf4jLogging

class BundleProjectFacade (
    maven: => Option[Maven] = None, 
    obrFacade: => Option[ObrFacade] = None, 
    commandletManager: => Option[CommandletManager] = None,
    repositoryAdmin: => Option[RepositoryAdmin] = None
) extends Slf4jLogging {
    
    /**
     * Takes the given project data and generates a Maven project using the default OSGi bundle project generator.
     * Requires the given targetDir to be non-existing because we don't want to overwrite anything.
     *
     * @param linkSourceFile if true, creates a symbolic link to the original source file
     */
    def generateBundleProject(projectData: ProjectData, targetDir: File, linkSourceFile: Boolean = false) {
        requireTargetDirDoesntExist(targetDir)
        targetDir.mkdirs
        BundleProjectGenerator.generateBundleProject(projectData, targetDir, linkSourceFile)
    }

    /**
     * Like {@link #generateBundleProject}, but only generates the "pom.xml" or updates it.
     */
    def generateBundleProjectPom(projectData: ProjectData, targetDir: File) {
        BundleProjectGenerator.generateBundleProjectPom(projectData, targetDir)
    }
    
    /**
     * Takes the given project data objects and generates for each of them a Maven bundle project along with a Maven 
     * reactor project allowing to build them all together.
     */
    def generateBundleProjects(projectDatas: Iterable[ProjectData], targetDir: File, linkSourceFiles: Boolean = false) {
        requireTargetDirDoesntExist(targetDir)
        targetDir.mkdirs
        val moduleNames = projectDatas.map { d =>
            val moduleName = generateSymbolicName(d)
            val subDir = targetDir / moduleName
            generateBundleProject(d, subDir, linkSourceFiles)
            moduleName
        }
        (targetDir / "pom.xml").content = generateReactorPomContent(moduleNames)    
    }

    private def requireTargetDirDoesntExist(targetDir: File) {
        require(!targetDir.exists, "target directory [" + targetDir + "] already exists")
    }
    
    /**
     * Builds the given project as a bundle and installs it to the local repository writing Maven output into
     * the given output stream. Requires Maven.
     *
     * @return Maven exit code
     */
    def installBundleProject(projectData: ProjectData, outputStream: OutputStream) = {
        val symbolicName = generateSymbolicName(projectData)
        val targetDir = RichFile.tempDir / symbolicName
        targetDir.makeSureDirExistsAndIsEmpty
        BundleProjectGenerator.generateBundleProject(projectData, targetDir)
        maven match {
            case Some(m) =>
                val pb = m.prepareExecution(baseDir = targetDir, arguments = List("clean", "install")) #> outputStream
                if (pb.! != 0) {
                    error("Maven build failed")
                }
            case None =>
                error("Maven not available")
        }
    }
    
    /**
     * Deploys the project into the running OSGi container. Requires OBR Facade.
     */
    def deployBundleProject(projectData: ProjectData, outputStream: OutputStream, deployProjectDataObrResources: Boolean = false) {
        installBundleProject(projectData, outputStream)
        obrFacade match {
            case Some(f) =>
                f.installedLocalRepositories.foreach(f.refreshRepository)
                if (deployProjectDataObrResources) {
                    deployObrResources(projectData)
                }
                val symbolicName = generateSymbolicName(projectData)
                val resourceId = ResourceId(symbolicName, projectData.projectId.version)
                f.switchOnOrRefreshResource(resourceId)
                
            case None =>
                error("ObrFacade not available")
        }
    }
    
    /**
     * Executes the commandlet mentioned in the given project data. The commandlet is executed in the running OSGi container
     * within the given execution context. The project is deployed, executed and after that immediately undeployed again.
     * This is very useful if you have a commandlet which you don't want to install permanently into the container, for example
     * because it is not intended to be universally reusable. Just think of a build script in a project folder. It is not
     * very useful outside this project folder.
     */
    def executeBundleProject(projectData: ProjectData, executionContext: ExecutionContext, outputStream: OutputStream, 
            deployProjectDataObrResources: Boolean = false) {
            
        projectData.commandletId match {
            case Some(i) =>                    
                deployBundleProject(projectData, outputStream, deployProjectDataObrResources = deployProjectDataObrResources)
                try {
                    commandletManager match {
                        case Some(m) =>
                            val commandlet = m.findCommandlet(i)
                            commandlet match {
                                case Some(c) =>
                                    c.execute(executionContext)
                                case None =>
                                    error("no commandlet found for specified commandletId")
                            }
                        case None =>
                            error("CommandletManager not available")
                    }
                } finally {
                    undeployBundleProject(projectData)
                }
            case None =>
                error("commandletId not specified")
        }
    }
    
    private def deployObrResources(projectData: ProjectData) {
        obrFacade match {
            case Some(f) =>
                if (!projectData.obrRepositories.isEmpty) {
                    projectData.obrRepositories.filterNot(f.repositoryIsInstalled).foreach { url => 
                        log.debug("Add repository [" + url + "]")
                        f.installRepository(url)
                    }
                }
                if (!projectData.obrResources.isEmpty) {
                    val resourceIds = projectData.obrResources.map(id => ResourceId(id.symbolicName, id.version))
                    f.switchOnResources(resourceIds.filterNot(f.resourceIsSwitchedOn))
                }
            case None =>
                error("ObrFacade not available")
        }
    }
    
    private def undeployBundleProject(projectData: ProjectData) {
        val symbolicName = generateSymbolicName(projectData)
        obrFacade match {
            case Some(f) =>
                f.switchOffResource(ResourceId(symbolicName, projectData.projectId.version))
            case None =>
                error("ObrFacade not available")
        }
    }
    
    private def generateReactorPomContent(moduleNames: Iterable[String]) = {
        <project 
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">
            <modelVersion>4.0.0</modelVersion>
            <groupId>org.helgoboss</groupId>
            <artifactId>one-filer-parent</artifactId>
            <version>1.0</version>
            <packaging>pom</packaging>
            <modules>
                {
                    moduleNames.map(n => <module>{n}</module>)
                }
            </modules>
        </project>.toString
    }
        
    private def generateSymbolicName(projectData: ProjectData) = projectData.projectId.groupId + "." + projectData.projectId.artifactId
    
}